module ProxyPump
  module Daemonize

    def self.included(receiver)
      receiver.extend         ClassMethods
      receiver.send :include, InstanceMethods
    end

    module InstanceMethods

      attr_reader :options

      def initialize(options = {})
        @options = options
      end

      def daemonize?
        !!options[:daemonize]
      end

      def pidfile?
        !!pidfile
      end

      # A daemonized process should write out its PID to a file.
      #
      def pidfile
        options[:pidfile]
      end

      def logfile
        options[:logfile]
      end

      def logfile?
        !!logfile
      end

      private

        # Run in daemon.
        #
        def daemonize
          Process.daemon(true)
        end

        # Write PID to file.
        #
        def write_pidfile
          if pidfile?
            max_retry_time = 3
            begin
              FileUtils.mkdir_p(File.dirname(pidfile), mode: 0755)
              File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) { |f| f.write Process.pid }
              at_exit { delete_pidfile }
            rescue Errno::EEXIST
              check_pid!
              if (max_retry_time -= 1) > 0
                sleep 0.5
                retry
              end
            end
          end
        end

        def trap_signals
          # Gracefully shutdown
          %i(QUIT INT TERM).each do |signal|
            trap(signal) do
              $stdout.puts "=== #{self.class.name} Shutdown: #{Time.now.utc} ==="
              $stdout.puts '- Goodbye!'
              exit
            end
          end
        end

        def check_pid!
          case pidfile_process_status
          when :running, :not_owned
            $stderr.puts "A server is already running. Check #{pidfile}"
            exit 1
          when :dead
            delete_pidfile
          end
        end

        def delete_pidfile
          File.unlink(pidfile) if ::File.exists? pidfile
        end

        def pidfile_process_status
          begin
            return :exited unless ::File.exists? pidfile
            pid = File.read(pidfile).to_i
            return :dead if pid.zero?
            # Check the process status.
            # The keys and values of {Signal.list} are known signal names and numbers, respectively.
            Process.kill(0, pid)
            :running
          rescue Errno::ESRCH
            :dead
          rescue Errno::EPERM
            :not_owned
          end
        end

        # If a PID file specified we must redirect $stdout and $stderror.
        # If no PID file specified but daemonize, we must disconnect $stdout and $stderr
        # from the terminal and suppress the output
        #
        def redirect_io
          if logfile?
            FileUtils.mkdir_p(File.dirname(logfile), mode: 0755)
            FileUtils.touch logfile
            File.chmod(0644, logfile)
            $stderr.reopen(logfile, 'a')
            $stdout.reopen($stderr)
            $stdout.sync = $stderr.sync = true
          else
            $stderr.reopen('/dev/null', 'a')
            $stdout.reopen($stderr)
            $stdout.sync = $stderr.sync = true
          end
      end
    end # InstanceMethods

    module ClassMethods
    end # ClassMethods

  end # Daemonize
end # ProxyPump
